use crate::errors::MyError;
use crate::models::{get_target, ApiMappingList, StaticMappingList};
use crate::notes_redis_helper::Auth;
use crate::result::MyResult;
use crate::utils::{get_content_type, get_referer, get_token, target_url};
use actix_web::{web, HttpRequest, HttpResponse};
use log::{debug, info};
use redis::Connection;
use redis_pool::SingleRedisPool;
use reqwest::{Client, Response};

async fn request(
    client: &Client,
    req: &HttpRequest,
    target_url: &str,
    body: &web::Bytes,
    user_id: Option<&str>,
) -> MyResult<Response> {
    info!("导向:{}->{}", &req.method(), target_url);

    // 转发请求到目标 URL，并返回响应
    let response = client
        .request(req.method().clone(), target_url)
        .header("Content-Type", get_content_type(req))
        .header("Referer", get_referer(req))
        .header("user-id", user_id.unwrap_or_default())
        .body(body.to_owned())
        .send()
        .await
        .map_err(MyError::ReqwestError)?;
    Ok(response)
}

async fn handle_static_proxy(
    client: &Client,
    req: &HttpRequest,
    target_url: &str,
) -> MyResult<Response> {
    request(client, req, target_url, &web::Bytes::new(), None).await
}
async fn handle_api_proxy_with_no_auth(
    client: &Client,
    req: &HttpRequest,
    target_url: &str,
    body: &web::Bytes,
) -> MyResult<Response> {
    // 转发请求到目标 URL，并返回响应
    request(client, req, target_url, body, None).await
}
async fn handle_api_proxy_with_auth(
    client: &Client,
    req: &HttpRequest,
    target_url: &str,
    body: &web::Bytes,
    redis_pool: &SingleRedisPool,
) -> MyResult<Response> {
    debug!("handle_api_proxy_with_auth");
    let token = get_token(req)?;
    debug!("get_token");
    let conn: &mut Connection = &mut redis_pool.factory().get_connection()?;

    //print_type(&what);
    if !Auth::from(conn).is_authed(&token)? {
        debug!("not authed:{}", &token);
        return Err(MyError::NotAuthed);
    }
    debug!("authed");
    // 转发请求到目标 URL，并返回响应
    debug!("request");
    let user_id = Auth::from(conn).get_user_id(&token)?;
    request(client, req, target_url, body, Some(&user_id)).await
}

async fn build_response(response: Response) -> HttpResponse {
    let mut proxy_res = HttpResponse::build(response.status());
    let headers = response.headers();
    for (header_name, header_value) in headers.iter() {
        proxy_res.append_header((header_name.clone(), header_value.clone()));
    }
    proxy_res.body(response.bytes().await.unwrap())
}

fn generate_full_path(req: &HttpRequest) -> String {
    let url = req.uri().path();
    let query_string = req.query_string();
    [url, query_string]
        .iter()
        .filter(|item| !item.is_empty())
        .map(|item| item.to_string())
        .collect::<Vec<String>>()
        .join("?")
}

async fn handle_static_route(
    client: &Client,
    req: &HttpRequest,
    static_mapping_list: &StaticMappingList,
    full_path: &str,
) -> std::result::Result<HttpResponse, MyError> {
    if let Some(target) = get_target(&static_mapping_list.0, req.uri().path()) {
        info!("静态url---->:{}", &req.uri().path());
        Ok(
            build_response(handle_static_proxy(client, req, &target_url(target, full_path)).await?)
                .await,
        )
    } else {
        Err(MyError::NotFound)
    }
}

async fn handle_api_route(
    client: &web::Data<Client>,
    req: &HttpRequest,
    api_mapping_list: &ApiMappingList,
    full_path: &str,
    body: &web::Bytes,
    redis_pool: &Option<SingleRedisPool>,
) -> std::result::Result<HttpResponse, MyError> {
    if let Some(target) = get_target(&api_mapping_list.0, req.uri().path()) {
        info!("api url---->:{}", &req.uri().path());
        Ok(build_response(if let Some(redis_pool) = redis_pool {
            handle_api_proxy_with_auth(
                client,
                req,
                &target_url(target, full_path),
                body,
                redis_pool,
            )
            .await?
        } else {
            handle_api_proxy_with_no_auth(client, req, &target_url(target, full_path), body).await?
        })
        .await)
    } else {
        Err(MyError::NotFound)
    }
}

pub async fn handle_proxy(
    req: HttpRequest,
    body: web::Bytes,
    redis_pool: web::Data<Option<SingleRedisPool>>,
    api_mapping_list: web::Data<ApiMappingList>,
    static_mapping_list: web::Data<StaticMappingList>,
    client: web::Data<Client>,
) -> std::result::Result<HttpResponse, MyError> {
    let full_path = generate_full_path(&req);
    match handle_static_route(&client, &req, &static_mapping_list, &full_path).await {
        Ok(result) => return Ok(result),
        Err(err) => {
            println!("-----not found");
            if let MyError::NotFound = err {
                // not found 不用处理，因为要处理api_route
            } else {
                return Err(err);
            }
        }
    };

    handle_api_route(
        &client,
        &req,
        &api_mapping_list,
        &full_path,
        &body,
        &redis_pool,
    )
    .await
}
